package olgo;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by golubinskyi on 18.12.13.
 */
public class MTTest {

    private static Thread.UncaughtExceptionHandler errh =
            new Thread.UncaughtExceptionHandler(){

                public void uncaughtException(Thread t, Throwable e) {
                    e.printStackTrace();
                }
            };


    public static void main(String[] args) {
        listWrapperTesting();
        accountTesting();
        waitNotifyTest();
        atomicTest();
    }

    private static void listWrapperTesting() {
        final SetListWrapper slw = new SetListWrapper(new LinkedList<Integer>());

        Runnable work = new Runnable() {
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    slw.offer(i);
                }
                if (SetListWrapper.containsDuplicates(slw.values())) {
                    throw new RuntimeException("MT ERROR " + Arrays.toString(slw.values().toArray()));
                }
            }
        };

        Thread t1 = new Thread(work);
        Thread t2 = new Thread(work);

        t1.setUncaughtExceptionHandler(errh);
        t2.setUncaughtExceptionHandler(errh);

        t1.start();
        t2.start();

        System.out.println("Wrapper testing done!");

    }

    private static void accountTesting() {
        final Account a = new Account(100);

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                int initialValue = a.getAmount();
                for (int i = 0; i < 10000; i++) {
                    synchronized (a){
                        a.withdraw(50); //paypal check
                        a.deposit(50);
                    }
                }
                if (a.getAmount() != initialValue)
                    throw new RuntimeException("MT ERROR " + a.getAmount() + " expected " + initialValue);
            }
        });

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                int initialValue = a.getAmount();
                for (int i = 0; i < 10000; i++) {
                    synchronized (a) {
                        a.withdraw(60);
                        a.deposit(60);
                    }
                }
                if (a.getAmount() != initialValue)
                    throw new RuntimeException("MT ERROR " + a.getAmount() + " expected " + initialValue);
            }
        });

        t1.setUncaughtExceptionHandler(errh);
        t2.setUncaughtExceptionHandler(errh);

        t1.start();
        t2.start();

        System.out.println("Account testing done!");
    }

    private static void waitNotifyTest() {
        class I {
            private int status = 0;
            public void inc() {
                status++;
            }
            public int getStatus () {return status;}
        }

        final I i = new I();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                synchronized (i) {
                    while (i.getStatus() < 42)
                        try {
                            i.wait();
                        } catch (InterruptedException e) {
                            System.err.println("T1 has been iterrupted");
                            throw new RuntimeException(e);
                        }
                    System.out.println("T1 - Bingo");
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                synchronized (i) {
                    while (i.getStatus() < 42)
                        i.inc();
                    i.notify();
                }
                System.out.println("T2 - Done");
            }
        });

        t1.setUncaughtExceptionHandler(errh);
        t2.setUncaughtExceptionHandler(errh);

        t1.start();
        t2.start();

        System.out.println("Wait/Notify  testing done!");
    }

    private static void atomicTest() {

        final int[] vals = new int[100];
        Arrays.fill(vals, 0);
        Runnable work = new Runnable() {
            public void run() {
                for (int j = 0; j < 100000; j++) {
                    for (int i = 0; i < vals.length; i++) {
                            vals[i]++;
                    }
                }
            }
        };

        Thread t1 = new Thread(work);
        Thread t2 = new Thread(work);

        t1.setUncaughtExceptionHandler(errh);
        t2.setUncaughtExceptionHandler(errh);

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            System.err.println("Atomic ops - join failed");
            throw new RuntimeException(e);
        }

        for (int i = 0; i < vals.length; i++) {
            if (vals[i] != 200000)
                throw new RuntimeException("MT TEST. ++ is not atomic : " + vals[i]);
        }

        System.out.println("Atomic ++ testing done!");
    }
}

class Account {
    private volatile int amount;

    public Account(int amount) {
        this.amount = amount;
    }

    public synchronized int getAmount(){return amount;}

    public void deposit(int amount) {

        synchronized (this) {
            this.amount = this.amount + amount;
        }

    }

    public synchronized void withdraw(int amount) {
        if (this.amount >= amount)
            this.amount = this.amount - amount;
    }

    public static synchronized void getBlah (){
        synchronized (Account.class){

        }
    }
}

class SetListWrapper {

    //structure contains no duplicates at each instant

    private List<Integer> internal;

    public SetListWrapper(List<Integer> listToWrap) {
        if (listToWrap == null || !listToWrap.isEmpty())
            throw new IllegalArgumentException("ivalid list " + listToWrap);
        internal = listToWrap;
    }

    public synchronized void offer(Integer i) {
        if (!internal.contains(i))
            internal.add(i);
    }

    public List<Integer> values() {
        return internal;
    }

    public static boolean containsDuplicates(List<Integer> list) {
        return list.size() != new HashSet<Integer>(list).size();
    }
}
